package main

import "fmt"

func main() {
	var n int
	_, err := fmt.Scanf("%d\n", &n)
	if err != nil {
		panic(err)
	}
	if n > maxN {
		panic("数量超出")
	}
	for i := 0; i < n; i++ {
		_, err := fmt.Scanf("%d%d%d\n", &x[i], &y[i], &z[i])
		if err != nil {
			panic(err)
		}
	}
	fmt.Println("===========================")
	for i := 0; i < n; i++ {
		fmt.Println(x[i], y[i], z[i])
	}
	fmt.Println("最大高度：", babylon(n)) // TODO:只知道结果，那么如何堆叠呢？是不是要保存堆叠路径
}

/*
叠砖块问题，上面的砖块底面长宽必须严格小于下面砖块的长宽，最高叠的高度是多少？
相同砖块可以重复使用，砖块数量无穷多。
*/

const maxN = 10000

var x, y, z [maxN]int
var dp [maxN][3]int

type floorType int8

const (
	xy floorType = iota
	xz
	yz
)

func babylonSub(c int, f floorType, n int) (h int) {
	if dp[c][f] != -1 {
		return dp[c][f]
	}

	var base1, base2 int
	switch f {
	case xy:
		base1 = x[c]
		base2 = y[c]
	case xz:
		base1 = x[c]
		base2 = z[c]
	case yz:
		base1 = y[c]
		base2 = z[c]
	}

	dp[c][f] = 0 //初始化当前高度为0，表示已经处理过，然后寻找最大高度

	for i := 0; i < n; i++ {
		//xy
		if x[i] < base1 && y[i] < base2 || x[i] < base2 && y[i] < base1 {
			dp[c][f] = max(dp[c][f], babylonSub(i, xy, n)+z[i])
		}
		//xz
		if x[i] < base1 && z[i] < base2 || x[i] < base2 && z[i] < base1 {
			dp[c][f] = max(dp[c][f], babylonSub(i, xz, n)+y[i])
		}
		//yz
		if y[i] < base1 && z[i] < base2 || y[i] < base2 && z[i] < base1 {
			dp[c][f] = max(dp[c][f], babylonSub(i, yz, n)+x[i])
		}
	}

	return dp[c][f]
}

func babylon(n int) (h int) {
	for i := 0; i < n; i++ {
		dp[i][0] = -1
		dp[i][1] = -1
		dp[i][2] = -1
	}
	for i := 0; i < n; i++ {
		h = max(h, babylonSub(i, xy, n)+z[i])
		h = max(h, babylonSub(i, xz, n)+y[i])
		h = max(h, babylonSub(i, yz, n)+x[i])
	}
	return
}
